package com.xjm.childmodule.server;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xjm.childmodule.constant.CSProtocol;
import com.xjm.childmodule.constant.Constant;
import com.xjm.childmodule.constant.SCProtocol;
import com.xjm.childmodule.entity.GameFrame;
import com.xjm.childmodule.entity.Packet;
import com.xjm.childmodule.myenum.UserStatusEnum;
import com.xjm.childmodule.util.MyUUIDUtils;
import com.xjm.childmodule.util.ServerUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Component
@Slf4j
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    static ServerUtil serverUtil;

    private String uname;

    static Lock lock = new ReentrantLock();

    static Condition matchCondition = lock.newCondition();

    @Autowired
    public void setServerUtil(ServerUtil serverUtil) {
        MyWebSocketHandler.serverUtil = serverUtil;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //添加到channelGroup通道组
        MyChannelHandlerPool.channelGroup.add(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("[{}]，websocket 断开", uname);

        UserStatusEnum status = serverUtil.getUserStatus(uname);
        if (status != null) {
            //用户处于游戏中
            if (status.compareTo(UserStatusEnum.IN_GAME) == 0) {
                //若房间中的另一位玩家也是处于待重连状态，那么就解散该房间，并设置用户为下线状态
                String opponent = serverUtil.getUserFromRoom(uname);
                if(serverUtil.getUserStatus(opponent).compareTo(UserStatusEnum.WAIT_RECONNECT) == 0){
                    //移除客户端
                    serverUtil.removeClinet(uname);
                    serverUtil.removeClinet(opponent);
                    //设置为下线状态
                    serverUtil.setUserOffLine(uname);
                    serverUtil.setUserOffLine(opponent);
                    //解除房间
                    serverUtil.removeUserFromRoom(uname);
                    serverUtil.removeUserFromRoom(opponent);
                    //移除游戏信息-
                    serverUtil.removeGameFrame(uname);
                    serverUtil.removeGameFrame(opponent);
                    //删除lockKey
                    serverUtil.removeLockKey(uname);
                    serverUtil.removeLockKey(opponent);
                }else{
                    //设置用户状态为待重连
                    serverUtil.setUserWaitReConnect(uname);
                    //移除客户端
                    serverUtil.removeClinet(uname);
                }
            }
            //用户处于其他状态
            else {
                //移除客户端
                serverUtil.removeClinet(uname);
                //设置为下线状态
                serverUtil.setUserOffLine(uname);
                //解除房间
                serverUtil.removeUserFromRoom(uname);
                //移除游戏信息
                serverUtil.removeGameFrame(uname);
                //删除lockKey
                serverUtil.removeLockKey(uname);
            }
        }

        //channelGroup 通道组 移除
        MyChannelHandlerPool.channelGroup.remove(ctx.channel());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //首次连接是FullHttpRequest，处理参数
        if (null != msg && msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            String uri = request.uri();

            Map paramMap=getUrlParams(uri);
            System.out.println("接收到的参数是："+ JSON.toJSONString(paramMap));

            String uname = paramMap.get("uname").toString();
            this.uname = uname;
            serverUtil.addClient(uname,ctx.channel());

            //如果url包含参数，需要处理
            if(uri.contains("?")){
                String newUri=uri.substring(0,uri.indexOf("?"));
                request.setUri(newUri);
            }

            //获取用户状态
            UserStatusEnum status = serverUtil.getUserStatus(uname);
            if (status != null) {
                //若用户待重连，重新连接后，继续使用保存一定时间的数据
                if (status.compareTo(UserStatusEnum.WAIT_RECONNECT) == 0) {
                    log.info("[{}]，用户重连", uname);
                    //设置用户状态为游戏中
                    serverUtil.setUserInGame(uname);
                    //发送最新帧
                    sendLatestFrame(uname);
                }
                //用户正常上线
                else if (status.compareTo(UserStatusEnum.OFFLINE) == 0) {
                    log.info("[{}]，用户上线", uname);
                    serverUtil.setUserOnLine(uname);
                }
                //用户已经在线
                else if (status.compareTo(UserStatusEnum.ONLINE) == 0) {
                    log.info("[{}]，用户已在线", uname);
                }
            } else {
                //新加入用户，设置状态为在线状态
                log.info("[{}]，新用户上线", uname);
                serverUtil.setUserOnLine(uname);
            }

        }
        //正常的TEXT消息类型
        else if(msg instanceof TextWebSocketFrame){
            TextWebSocketFrame frame=(TextWebSocketFrame)msg;
            //json数据
            System.out.println("客户端发送到服务端的消息：" +frame.text());

            JSONObject jsonObject = JSON.parseObject(frame.text());
            Integer protocol = (Integer) jsonObject.get("protocol");

            if (protocol.equals(CSProtocol.ADD_USER)) {
                addUser();
            } else if (protocol.equals(CSProtocol.MATCH_USER)) {
                matchUser();
            } else if (protocol.equals(CSProtocol.CANCEL_MATCH)) {
                cancelMatch();
            } else if (protocol.equals(CSProtocol.MYSELF_FRAME)) {
                mySelfGame(jsonObject);
            }else if (protocol.equals(CSProtocol.OPPONENT_FRAME)) {
                opponentGame(jsonObject);
            } else if (protocol.equals(CSProtocol.GAME_OVER)) {
                gameOver();
            }
        }
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {

    }

    //************************ 与客户端对话相关 Begin ***************************
    /**
     * 判断客户端是否还连接
     * @param receiver 客户端
     * @return
     */
    public boolean validUser(String receiver) {
        return serverUtil.getClient(receiver) != null;
    }

    /**
     *  向客户端发包
     * @param receiver 接收者
     * @param protocol sc协议
     * @param code 响应码 成功/失败
     * @param desc 描述
     */
    public void sendPacket(String receiver, Integer protocol ,Integer code ,String desc ) {
        Packet packet = new Packet();
        packet.setProtocol(protocol);
        packet.setCode(code);
        packet.setDesc(desc);

        Channel channel = serverUtil.getClient(receiver);
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(JSON.toJSONString(packet)),new MyMacher(channel.id().asLongText()));
    }

    /**
     *  向客户端发包 携带数据
     * @param receiver 接收者
     * @param protocol sc协议
     * @param code 响应码 成功/失败
     * @param desc 描述
     * @param data 数据
     */
    public void sendPacket(String receiver, Integer protocol ,Integer code ,String desc , Object data) {
        Packet packet = new Packet();
        packet.setProtocol(protocol);
        packet.setCode(code);
        packet.setDesc(desc);
        packet.setData(data);

        Channel channel = serverUtil.getClient(receiver);
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(JSON.toJSONString(packet)),new MyMacher(channel.id().asLongText()));
    }

    /**
     *  向客户端发帧
     * @param receiver 接收者
     * @param protocol sc协议
     * @param data 数据
     */
    public void sendFrame(String receiver, Integer protocol , Object data) {
        Packet packet = new Packet();
        packet.setProtocol(protocol);
        packet.setData(data);
        packet.setDesc("frame");

        Channel channel = serverUtil.getClient(receiver);
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(JSON.toJSONString(packet)),new MyMacher(channel.id().asLongText()));
    }

    /**
     * 回复客户端
     *
     * @param protocol 协议
     * @param receiver 客户端
     * @param code     响应码
     * @param desc     描述
     */
    @SneakyThrows
    public void responseToClient(Integer protocol, String receiver, Integer code, String desc) {
        if(validUser(receiver)){
            sendPacket(receiver,protocol,code,desc);
        }
    }

    /**
     * 回复客户端 携带数据
     *
     * @param protocol 协议
     * @param receiver 客户端
     * @param code     响应码
     * @param desc     描述
     * @param data     数据
     */
    @SneakyThrows
    public void responseToClient(Integer protocol, String receiver, Integer code, String desc, Object data) {
        if(validUser(receiver)){
            sendPacket(receiver,protocol,code,desc,data);
        }
    }

    /**
     * 推送帧
     *
     * @param protocol 协议
     * @param receiver 客户端
     * @param data     数据
     */
    @SneakyThrows
    public void FrameToClient(Integer protocol, String receiver, Object data) {
        if(validUser(receiver)){
            sendFrame(receiver,protocol,data);
        }
    }

    /**
     * 玩家重连后，推送最新帧
     * @param receiver 重连玩家
     */
    public void sendLatestFrame(String receiver){
        //对手
        String opponent = serverUtil.getUserFromRoom(receiver);

        List<GameFrame> receiverGameFrameList = serverUtil.getGameFrameList(receiver);
        List<GameFrame> opponentGameFrameList = serverUtil.getGameFrameList(opponent);

        responseToClient(SCProtocol.MATCH_USER_RESPONSE,receiver,200,"玩家重连",opponent);

        //新创建协议，专门用于发送重连后的数据 RECONNECT_FRAME
        HashMap frameMap = new HashMap();
        frameMap.put("myselfFrame",receiverGameFrameList.get(receiverGameFrameList.size()-1));
        frameMap.put("opponentFrame",opponentGameFrameList.get(opponentGameFrameList.size()-1));

        FrameToClient(SCProtocol.RECONNECT_FRAME,receiver,frameMap);
    }

    //************************ 与客户端对话相关 End ***************************

    /**
     * 指定用户发送消息
     * @param jsonStr
     */
    private void sendMessageTo(String jsonStr,ChannelId channelId) {
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(jsonStr),new MyMacher(channelId.asLongText()));
    }

    private void sendAllMessage(String jsonStr){
        //收到信息后，群发给所有channel
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(jsonStr));
    }

    private static Map getUrlParams(String url){
        Map<String,String> map = new HashMap<>();
        url = url.replace("?",";");
        if (!url.contains(";")){
            return map;
        }
        if (url.split(";").length > 0){
            String[] arr = url.split(";")[1].split("&");
            for (String s : arr){
                String key = s.split("=")[0];
                String value = s.split("=")[1];
                map.put(key,value);
            }
            return  map;

        }else{
            return map;
        }
    }

    //************************ 游戏业务 Begin ***************************

    /**
     * 用户加入匹配大厅
     */
    private void addUser() {
        if(serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.ONLINE) != 0){
            return;
        }
        //设置用户为待匹配状态
        serverUtil.setUserWaitMatch(uname);
        log.info("[{}]加入匹配大厅", uname);
        //回复客户端
        responseToClient(SCProtocol.ADD_USER_RESPONSE, uname, Constant.SUCCESS,"成功加入大厅");
    }

    /**
     * 用户随机匹配
     */
    @SneakyThrows
    private void matchUser() {
        if(serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.WAIT_MATCH) != 0){
            return;
        }

        lock.lock();
        try {
            // 设置用户状态为匹配中
            serverUtil.setUserInMatch(uname);
            matchCondition.signal();
        } finally {
            lock.unlock();
        }

        // 创建一个异步线程任务，负责匹配其他同样处于匹配状态的其他用户
        Thread matchThread = new Thread(() -> {
            boolean flag = true;
            String opponent = null;
            //自身不断循环
            while (flag) {
                // 获取除自己以外的其他待匹配用户
                lock.lock();
                try {
                    // 当前用户处于游戏中或者游戏结束状态，不在匹配中状态
                    // 别的线程唤醒时，当前用户的状态已经被别的线程修改，所以获取的状态不是匹配中
                    if (serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.IN_GAME) == 0
                            || serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.OFFLINE) == 0) {
                        return;
                    }
                    // 当前用户取消匹配
                    if (serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.WAIT_MATCH) == 0) {
                        return;
                    }
                    //随机获取除自己以外的其他待匹配用户
                    opponent = serverUtil.getUserInMatchRandom(uname);
                    if (opponent != null) {
                        // 对手不处于待匹配状态
                        if (serverUtil.getUserStatus(opponent).compareTo(UserStatusEnum.IN_MATCH) != 0) {
                            log.info("当前用户[{}]匹配到的对手[{}]已退出匹配状态", uname, opponent);
                        } else {
                            //成功匹配到对手
                            log.info("当前用户[{}]成功匹配到对手[{}]", uname, opponent);

                            serverUtil.setUserInGame(uname);
                            serverUtil.setUserInGame(opponent);
                            serverUtil.setUserInRoom(uname, opponent);
                            flag = false;

                            //向匹配成功的两位玩家发送匹配成功的消息
                            responseToClient(SCProtocol.MATCH_USER_RESPONSE, uname, Constant.SUCCESS, "匹配对手成功", opponent);
                            responseToClient(SCProtocol.MATCH_USER_RESPONSE, opponent,Constant.SUCCESS, "匹配对手成功", uname);

                        }
                    } else {
                        // 如果当前没有待匹配用户，进入等待队列
                        try {
                            log.info("当前用户[{}]暂无对手可匹配，等待中", uname);
                            matchCondition.await();
                        } catch (InterruptedException e) {
                            log.error("匹配线程[{}]发生异常: [{}]",
                                    Thread.currentThread().getName(), e.getMessage());
                        }
                    }
                } finally {
                    lock.unlock();
                }
            }

            //二、初始化游戏帧
            GameFrame gameFrame = new GameFrame();
            gameFrame.setText("init");
            gameFrame.setSteps(0);
            serverUtil.setGameFrame(uname, gameFrame);
            serverUtil.setGameFrame(opponent, gameFrame);

            //设置分布式锁 lockKey
            // todo
            // 不知道并发环境下会不会产生相同的uuid
            String unameLockKey = MyUUIDUtils.getUUID(uname);
            String opponentLockKey = MyUUIDUtils.getUUID(opponent);
            serverUtil.setLockKey(uname,unameLockKey);
            serverUtil.setLockKey(opponent,opponentLockKey);

            //服务器推送帧
            FrameToClient(SCProtocol.SERVER_PUSH_MYSELF_FRAME,uname,gameFrame);
            FrameToClient(SCProtocol.SERVER_PUSH_MYSELF_FRAME,opponent,gameFrame);

            FrameToClient(SCProtocol.SERVER_PUSH_OPPONENT_FRAME,uname,gameFrame);
            FrameToClient(SCProtocol.SERVER_PUSH_OPPONENT_FRAME,opponent,gameFrame);

        }, Constant.MATCH_TASK_NAME_PREFIX + "[" + uname + "]");
        matchThread.start();
    }

    /**
     * 取消匹配
     */
    @SneakyThrows
    private void cancelMatch() {
        if(serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.IN_MATCH) != 0){
            return;
        }

        lock.lock();
        try {
            serverUtil.setUserWaitMatch(uname);
            log.info("[{}]取消匹配", uname);
            //回复客户端
            responseToClient(SCProtocol.MATCH_USER_RESPONSE, uname, Constant.FAIL, "用户取消匹配");
        } finally {
            lock.unlock();
        }
    }

    /**
     * 本方帧
     */
    @SneakyThrows
    public void mySelfGame(JSONObject jsonObject) {
        //若当前玩家不处于游戏中的状态，则直接返回
        if(serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.IN_GAME) != 0){
            return;
        }

        //尝试获取锁
        String lockKey = serverUtil.getLockKey(uname);
        //获取分布式锁
        boolean flag = serverUtil.tryGetDcsLock(lockKey, uname, 5);
        if(flag){

            //前端传来的玩家操作值
            GameFrame gameFrame = jsonObject.getObject("gameFrame", GameFrame.class);
            //前端传来的步骤数
            Integer steps = gameFrame.getSteps();
            String text = gameFrame.getText();

            List<GameFrame> gameFrameList = serverUtil.getGameFrameList(uname);
            Integer trueSteps = gameFrameList.size() - 1;

            //如果前端传来的步骤数小于服务端的步骤数，证明前端落后。需要重新获取最新帧
            if(steps <  trueSteps){
                System.out.println("前端传来的步骤数小于服务端的步骤数");
                FrameToClient(SCProtocol.SERVER_PUSH_MYSELF_FRAME,uname,gameFrameList.get(trueSteps));
                serverUtil.deleteDcsLock(lockKey);
                return;
            }

            //对手
            String opponent = serverUtil.getUserFromRoom(uname);

            //一、操作数据
            serverUtil.setGameFrame(uname, new GameFrame(text,trueSteps + 1));

            //二、推送帧
            FrameToClient(SCProtocol.SERVER_PUSH_MYSELF_FRAME,uname,new GameFrame(text,trueSteps + 1));
            FrameToClient(SCProtocol.SERVER_PUSH_OPPONENT_FRAME,opponent,new GameFrame(text,trueSteps + 1));

            serverUtil.deleteDcsLock(lockKey);


        }else{
            responseToClient(SCProtocol.OPPONENT_IS_OPERATIING,uname,Constant.FAIL,"对手正在操作，请稍后再试");
        }

    }

    /**
     * 对手帧
     */
    @SneakyThrows
    public void opponentGame(JSONObject jsonObject) {
        //若当前玩家不处于游戏中的状态，则直接返回
        if(serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.IN_GAME) != 0){
            return;
        }
        //对手
        String opponent = serverUtil.getUserFromRoom(uname);

        //尝试获取锁
        String lockKey = serverUtil.getLockKey(opponent);
        //获取分布式锁
        boolean flag = serverUtil.tryGetDcsLock(lockKey, opponent, 5);
        if(flag){

            //前端传来的玩家操作值
            GameFrame gameFrame = jsonObject.getObject("gameFrame", GameFrame.class);
            //前端传来的步骤数
            Integer steps = gameFrame.getSteps();
            String text = gameFrame.getText();

            List<GameFrame> gameFrameList = serverUtil.getGameFrameList(opponent);
            Integer trueSteps = gameFrameList.size() - 1;

            //如果前端传来的步骤数小于服务端的步骤数，证明前端落后。需要重新获取最新帧
            if(steps <  trueSteps){
                System.out.println("前端传来的步骤数小于服务端的步骤数");
                FrameToClient(SCProtocol.SERVER_PUSH_MYSELF_FRAME,opponent,gameFrameList.get(trueSteps));
                serverUtil.deleteDcsLock(lockKey);
                return;
            }

            //一、操作数据
            serverUtil.setGameFrame(opponent, new GameFrame(text,trueSteps + 1));

            //二、发送数据
            FrameToClient(SCProtocol.SERVER_PUSH_MYSELF_FRAME,opponent,new GameFrame(text,trueSteps + 1));
            FrameToClient(SCProtocol.SERVER_PUSH_OPPONENT_FRAME,uname,new GameFrame(text,trueSteps + 1));

            serverUtil.deleteDcsLock(lockKey);

        }else{
            responseToClient(SCProtocol.OPPONENT_IS_OPERATIING,uname,Constant.SUCCESS,"对手正在操作，请稍后再试");
        }

    }

    /**
     * 结束游戏
     */
    public void gameOver() {
        //若当前玩家不处于游戏中的状态，则直接返回
        if(serverUtil.getUserStatus(uname).compareTo(UserStatusEnum.IN_GAME) != 0){
            return;
        }

        //只要一方发起结束游戏，双方玩家都结束
        log.info("[{}]主动结束游戏", uname);

        String opponent = serverUtil.getUserFromRoom(uname);
        if(opponent == null){
            return;
        }
        log.info("[{}]被动结束游戏", opponent);

        responseToClient(SCProtocol.GAME_OVER_RESPONSE,uname, Constant.SUCCESS,"成功结束游戏");
        responseToClient(SCProtocol.GAME_OVER_RESPONSE,opponent,Constant.SUCCESS, "成功结束游戏");

        lock.lock();
        try {
            //设置为待匹配状态
            serverUtil.setUserWaitMatch(uname);
            serverUtil.setUserWaitMatch(opponent);

            //移除游戏信息
            serverUtil.removeGameFrame(uname);
            serverUtil.removeGameFrame(opponent);
            //解除房间
            serverUtil.removeUserFromRoom(uname);
            serverUtil.removeUserFromRoom(opponent);
            //删除lockKey
            serverUtil.removeLockKey(uname);
            serverUtil.removeLockKey(opponent);
        } finally {
            lock.unlock();
        }
    }
    //************************ 游戏业务 End ***************************
}
